
Simulator = function(spec) {
    this.data = $.extend({}, spec);

    this.directions = [];
    this.suggestionMarkers = {};

    this.taskControl = new TaskControl($('#tasks'));

    $('#simulate').click($.proxy(this.simulate, this));

    //var mapsApiKey = 'AIzaSyAUkWFq8F3fi4hWvZ3vkRrlXcy53e57l2Q';
    this.map = new google.maps.Map($(this.data.mapSelector).get(0), {
        zoom: 15,
        center: new google.maps.LatLng(40.945695, -73.831908),
        mapTypeId: google.maps.MapTypeId.ROADMAP
    });

    this.markers = [];

    this.directionsService = new google.maps.DirectionsService();
    this.bounds = new google.maps.LatLngBounds();

    this.extendBoundsUsingRouteProxy = $.proxy(this.extendBoundsUsingRoute, this);

    google.maps.event.addListener(this.map, 'click', $.proxy(this.mapClick, this));

    this.currentTime = new Date();
    this.updateCurrentTime(0);
    $('#current-time').change($.proxy(function() {
        var s = $('#current-time').val();
        var matches = s.match(/^([0-9]+)(:?[0-9]*)(am|pm)?$/);
        if (matches && matches.length >= 3) {
            var h = parseInt(matches[1]);
            var ms = matches[2] ? matches[2].substring(1) : '0';
            var m = parseInt(ms);

            var half = matches.length > 3 ? matches[3] : null;

            if ((h <= 12 || (!half && h < 24)) && m < 60) {
                if (half == 'pm') {
                    if (h != 12) h += 12;
                } else if (half == 'am') {
                    if (h == 12) h = 0;
                }

                var date = new Date();
                this.currentTime = new Date(date.getYear(), date.getMonth(), date.getDate(), h, m, 0, 0);
            }
        }
        this.updateCurrentTime(0);
    }, this));
};

Simulator.prototype = {
    start: function() {},

    simulate: function() {
        var events = this.computeEnvironmentSignalsFromDirections();
        var environment = new MockEnvironment({
            placeService: new GooglePlaceService({ map: this.map }),
            events: events
        });
        var assistant = new Assistant({
            environment: environment,
            tasks: this.taskControl.tasks
        });

        assistant.suggest($.proxy(function(task) {
            var key = task.location.lat + '.' + task.location.lng;
            var marker = this.suggestionMarkers[key];

            if (!marker) {
                var pinColor = '7569FE';
                var pinImage = new google.maps.MarkerImage('http://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=%E2%80%A2|' + pinColor,
                    new google.maps.Size(21, 34),
                    new google.maps.Point(0,0),
                    new google.maps.Point(10, 34));
                var pinShadow = new google.maps.MarkerImage('http://chart.apis.google.com/chart?chst=d_map_pin_shadow',
                    new google.maps.Size(40, 37),
                    new google.maps.Point(0, 0),
                    new google.maps.Point(12, 35));
                marker = new google.maps.Marker({
                    position: new google.maps.LatLng(task.location.lat, task.location.lng),
                    map: this.map,
                    clickable: true,
                    icon: pinImage
                });
                this.suggestionMarkers[key] = marker;
                var location = '<strong>' + task.location.name + '</strong><br/>';
                location += '<small>' + task.location.address +  '</small><br/>';
                var action = task.action + ' ' + (task.item || task.entity) + ' at ' + task.time;
                marker.infoWindow = new google.maps.InfoWindow({
                    content: location + action
                });
                marker.showInfoWindow = $.proxy(function() {
                    marker.infoWindow.open(this.map, marker);
                }, this);
                google.maps.event.addListener(marker, 'click', marker.showInfoWindow);
            }
            marker.showInfoWindow();
        }, this));

        assistant.start();
        environment.start();
    },

    computeEnvironmentSignalsFromDirections: function() {
        var signals = [];
        $.each(this.directions, function(_, direction) {
            var startTime = direction.startTime;
            $.each(direction.routes, function(_, route) {
                $.each(route.legs, function(_, leg) {
                    $.each(leg.steps, function(_, step) {
                        var f = step.path.length - 1;
                        var d = step.duration.value / f;
                        $.each(step.path, function(_, location) {
                            signals.push({
                                time: startTime.formatTimeOfDay(),
                                location: {
                                    address: null,
                                    lat: location.lat(),
                                    lng: location.lng()
                                }
                            });
                            startTime = startTime.addSeconds(d);
                        });
                    });
                });
            });
        });
        return signals;
    },

    updateCurrentTime: function(seconds) {
        this.currentTime = this.currentTime.addSeconds(seconds);
        $('#current-time').val(this.currentTime.formatTimeOfDay());
    },

    mapClick: function(event) {
        var position = event.latLng;
        var marker = new google.maps.Marker({
            position: position,
            map: this.map
        });

        new google.maps.Geocoder().geocode({
            location: position
        }, $.proxy(this.markerInfoRetrieved, this, marker));
    },

    markerInfoRetrieved: function(marker, responses, status) {
        if (status != google.maps.GeocoderStatus.OK || !responses.length) {
            return;
        }

        marker.formatted_address = responses[0].formatted_address;

        this.markers.push(marker);

        if (this.markers.length > 1) {
            var previousMarker = this.markers[this.markers.length - 2];
            var travelMode = this.guessTravelMode(previousMarker.getPosition(), marker.getPosition());
            var request = {
                travelMode: travelMode,
                origin: previousMarker.getPosition(),
                destination: marker.getPosition()
            };
            this.directionsService.route(request, $.proxy(this.directionsResponse, this, previousMarker, marker, travelMode));
        } else {
            var step = 'Start at ' + marker.formatted_address;
            $('#route').append('<li></li>').children(':last').text(step);
        }
    },

    travelModes: {
        driveOnly: google.maps.TravelMode.DRIVING,
        bikeOnly: google.maps.TravelMode.BICYCLING,
        transitOnly: google.maps.TravelMode.TRANSIT,
        walkOnly: google.maps.TravelMode.WALKING,
        walkAndDrive: [ google.maps.TravelMode.WALKING, google.maps.TravelMode.DRIVING ],
        walkAndTransit: [ google.maps.TravelMode.WALKING, google.maps.TravelMode.TRANSIT ]
    },

    guessTravelMode: function(position1, position2) {
        var selectedValue = $('#travel-mode').val();
        var travelModes = this.travelModes[selectedValue];
        if (!$.isArray(travelModes)) {
            travelModes = [travelModes, travelModes];
        }
        var distance = google.maps.geometry.spherical.computeDistanceBetween(position1, position2);
        var walkingDistance = distance < 500;
        return travelModes[walkingDistance ? 0 : 1];
    },

    directionsResponse: function(fromMarker, toMarker, travelMode, result, status) {
        if (status != google.maps.DirectionsStatus.OK) {
            return;
        }

        var color;
        var action;
        if (travelMode == google.maps.TravelMode.WALKING) {
            color = 'green';
            action = 'walk';
        } else if (travelMode == google.maps.TravelMode.BICYCLING) {
            color = 'blue';
            action = 'bike';
        } else if (travelMode == google.maps.TravelMode.TRANSIT) {
            color = 'magenta';
            action = 'transit';
        } else if (travelMode == google.maps.TravelMode.DRIVING) {
            color = 'red';
            action = 'drive';
        } else {
            color = null;
        }

        new google.maps.DirectionsRenderer({
            polylineOptions: { strokeColor: color, strokeOpacity: 0.5, strokeWeight: 5 },
            preserveViewport: true,
            suppressMarkers: true,
            directions: result,
            map: this.map
        });

        $.each(result.routes, this.extendBoundsUsingRouteProxy);
        this.map.fitBounds(this.bounds);

        var currentTime = $('#current-time').val();
        var step = 'at ' + currentTime + ', ' + action + ' to ' + toMarker.formatted_address;
        var duration = 0;
        $.each(result.routes, function(_, route) {
            $.each(route.legs, function(_, leg) {
                if (!leg.duration) {
                    duration = null;
                    return false;
                }
                duration += leg.duration.value;
            });
        });
        if (duration) {
            step += ' (' + Math.round(duration / 60) + 'min)';
            this.updateCurrentTime(duration);
        }
        $('#route').append('<li></li>').children(':last').text(step);

        this.directions = [];
        this.directions.push({ routes: result.routes, startTime: this.currentTime });
        this.simulate();
    },

    extendBoundsUsingRoute: function(_, route) {
        this.bounds = this.bounds.union(route.bounds);
    }
};

Date.prototype.formatTimeOfDay = function() {
    var hrs = this.getHours();
    var half = hrs < 12 ? 'am' : 'pm';
    hrs %= 12;
    if (hrs == 0) hrs = 12;
    if (hrs < 10) hrs = '0' + hrs;
    var mins = this.getMinutes();
    if (mins < 10) mins = '0' + mins;
    return hrs + ':' + mins + half;
};

Date.prototype.addSeconds = function(seconds) {
    return new Date(this.getTime() + 1000 * seconds);
};
